/*
 * CVideoRender.cpp
 *
 *  Created on: 2016年3月18日
 *      Author: terry
 */

#include "CVideoRender.h"
#include "NullDeleter.h"

#ifdef WIN32
#include "WinVideoWindow.h"
#if defined(_MSC_VER)
#include "WtlVideoWindow.h"
#else
#endif //
#endif //WIN32

#include "SdlVideoWindow.h"

#include "FfmpegUtil.h"
#include "TimeHelper.h"
#include "CLog.h"

#include "AppProperties.h"
#include "MediaProp.h"
#include "TStringUtil.h"
#include "MediaEvent.h"


namespace av
{

static const int MAX_SYNC_TIME = 2 * 1000 * 1000;
static const size_t MAX_QUEUE_SIZE = 80;


CVideoRender::CVideoRender() :
	m_videoWindow(),
	m_state(STATE_STOPPED),
	m_refClock(),
	m_nosync(),
	m_scale(),
	m_waitForKeyFrame(),
	m_isLive(),
	m_ratioX(),
	m_ratioY(),
	m_drawGraphMode(DRAW_GRAPH_NONE),
	m_eventSink()
{

	std::string renderName = AppProperties::getProperties().getString(MEDIA_VIDEO_PROP_RENDER);
	comn::StringUtil::toLower(renderName);

}

CVideoRender::~CVideoRender()
{
	close();
}

bool CVideoRender::getTimePoint(int64_t& clock, int64_t& pts)
{
	if (!m_curPoint.isSet())
	{
		return false;
	}

	clock = m_curPoint.m_clock;
	pts = m_curPoint.m_pts;
	return true;
}

void CVideoRender::close()
{
	if (getState() != STATE_STOPPED)
	{
		stop();
	}

	flush();

	if (m_videoWindow)
	{
		m_videoWindow->close();
	}

	if (isRunning())
	{
		comn::Thread::stop();
	}

	m_isLive = false;
}

bool CVideoRender::isOpen()
{
	return true;
}

int CVideoRender::play()
{
	setState(STATE_PLAYING);

	if (m_videoWindow)
	{
		m_videoWindow->setState(STATE_PLAYING);
	}

	postCommand(kPlay);

	return 0;
}

int CVideoRender::pause()
{
	setState(STATE_PAUSED);

	if (m_videoWindow)
	{
		m_videoWindow->setState(STATE_PAUSED);
	}

	postCommand(kPause);

	return 0;
}

void CVideoRender::stop()
{
	setState(STATE_STOPPED);

	if (m_videoWindow)
	{
		m_videoWindow->setState(STATE_STOPPED);
	}

	flush();

	postCommand(kStop);
}

int CVideoRender::getState()
{
	return m_state;
}

void CVideoRender::setFormat(const MediaFormat& fmt)
{

}

int CVideoRender::write(AVFramePtr frame)
{
	if (!frame)
	{
		return EINVAL;
	}

	if (m_state == STATE_STOPPED)
	{
		return EAGAIN;
	}

	if (m_isLive && (m_frames.size() > 4))
	{
		//CLog::debug("video frame. pts:%I64d, pkt(%I64d, %I64d) duration:%d, que:%d\n",
		//    frame->pts, frame->pkt_dts/1000, frame->pkt_pts/1000, (int)frame->pkt_duration, m_frames.size());
	}

	m_frames.push(frame);
	m_event.post();

	return 0;
}

void CVideoRender::flush()
{
	m_frames.clear();

	m_originPoint.reset();
	m_curPoint.reset();

	if (m_videoWindow)
	{
		m_videoWindow->flush();
	}
}


void CVideoRender::setScale(AVRational scale)
{
	postCommand(kSetScale, scale.num, scale.den);
}

bool CVideoRender::setProp(const std::string& name, const std::string& value)
{
	if (name == MEDIA_VIDEO_PROP_NOSYNC)
	{
		comn::AutoCritSec lock(m_cs);
		m_nosync = true;
	}
	else if (name == MEDIA_PLAYER_PROP_LIVE_MODE)
	{
		comn::AutoCritSec lock(m_cs);
		comn::StringCast::toValue(value, m_isLive);
	}
	else if (name == MEDIA_VIDEO_PROP_SHOW_FRAME)
	{
#ifdef WIN32
		FaceTextPainter::SHOW_PTS = true;
#endif //

	}
	return true;
}

int CVideoRender::open()
{
	comn::Thread::start();

	return 0;
}

void CVideoRender::setVideoWnd(void* hwnd)
{
	if (!m_videoWindow)
	{
		m_videoWindow.reset(createVideoWindow());
		if (!m_videoWindow)
		{
			return;
		}
	}


	if (m_videoWindow->isOpen())
	{
		m_videoWindow->close();
	}

	m_videoWindow->open(hwnd);
	m_videoWindow->enableDrawGraph(m_drawGraphMode);
	m_videoWindow->setSink(this);
}

bool CVideoRender::isFullscreen()
{
	if (!m_videoWindow)
	{
		return false;
	}
	return m_videoWindow->isFullscreen();
}

int CVideoRender::setFullscreen(bool full)
{
	if (!m_videoWindow)
	{
		return ENOENT;
	}
	return m_videoWindow->setFullscreen(full);
}

int CVideoRender::setBackImage(const char* filepath)
{
	//todo
	if (!m_videoWindow)
	{
		return ENOENT;
	}

	return m_videoWindow->setBackImage(filepath);
}


int CVideoRender::setRatio(int width, int height)
{
	m_ratioX = width;
	m_ratioY = height;

	if (m_videoWindow)
	{
		m_videoWindow->setRatio(width, height);
	}
	return 0;
}

void CVideoRender::getRatio(int& width, int& height)
{
	width = m_ratioX;
	height = m_ratioY;
}

void CVideoRender::setRefClock(ReferenceClock* refClock)
{
	m_refClock = refClock;
}

void CVideoRender::step()
{
	postCommand(kStep);
}

void CVideoRender::nextKeyFrame()
{
	postCommand(kNextKeyFrame);
}

void CVideoRender::setEventSink(RenderEventSink* pSink)
{
	m_eventSink = pSink;
}


void CVideoRender::setVideoWindow(VideoWindow* window)
{
	VideoWindowPtr videoWindow(window, comn::NullDeleter());
	m_videoWindow = videoWindow;
}

int CVideoRender::writeText(AVPacketPtr& pkt)
{
	if (m_videoWindow)
	{
		m_videoWindow->drawText(pkt.get());
	}

	return 0;
}

void CVideoRender::onCommand(Command& cmd)
{
	if (cmd.type == kPlay)
	{
		handlePlay();
	}
	else if (cmd.type == kPause)
	{
		handlePause();
	}
	else if (cmd.type == kStop)
	{
		handleStop();
	}
	else if (cmd.type == kSetScale)
	{
		AVRational scale;
		scale.num = cmd.at(0).toInt();
		scale.den = cmd.at(1).toInt();

		handleSetScale(scale);
	}
	else if (cmd.type == kStep)
	{
		handleStep();
	}
	else if (cmd.type == kNextKeyFrame)
	{
		handleNextKeyFrame();
	}
}

void CVideoRender::onIdle()
{
	int state = getState();
	if (state == STATE_STOPPED)
	{
		m_event.timedwait(-1);
		return;
	}
	else if (state == STATE_PAUSED)
	{
		if (m_waitForKeyFrame)
		{
			AVFramePtr frame = waitAndGet(-1);
			if (frame && frame->key_frame)
			{
				m_waitForKeyFrame = false;

				drawFrame(frame, getPts(frame));
			}
		}
		else
		{
			m_event.timedwait(-1);
		}
		return;
	}

	AVFramePtr frame = waitAndGet(-1);
	if (!frame)
	{
		return;
	}

	if (m_isLive)
	{
		if (m_frames.size() > MAX_QUEUE_SIZE)
		{
			size_t count = m_frames.popUntilSize(2);
			CLog::warning("CVideoRender drop %d frames.\n", count);

			m_originPoint.reset();
			m_curPoint.reset();
			return;
		}
	}

	int delay = 0;
	int64_t pts = getPts(frame);
	if ((pts == AV_NOPTS_VALUE) || (pts < 0))
	{
		delay = frame->pkt_duration;
		m_originPoint.reset();
		m_curPoint.reset();
	}
	else
	{
		if (!m_originPoint.isSet() || (m_originPoint.m_pts == AV_NOPTS_VALUE))
		{
			m_originPoint.reset(getClock(), pts);

			CLog::debug("reset origin point(%lld, %lld).\n", m_originPoint.m_clock, m_originPoint.m_pts);
		}
		else if (m_curPoint.isSet())
		{
			if ((pts - m_curPoint.m_pts) >= MAX_SYNC_TIME)
			{
				m_originPoint.reset(getClock(), pts);
				m_curPoint.reset();

				CLog::debug("reset origin point(%lld, %lld) for max sync.\n", m_originPoint.m_clock, m_originPoint.m_pts);
			}
		}

		delay = computeDelay(frame);
	}

	// if (m_frames.size() > 4)
	// {
		//CLog::debug("video frame. queue:%d, pts:%I64d, delay:%d, duration:%d\n",
		//		m_frames.size(), frame->pts, delay, (int)frame->pkt_duration);
	// }

	if (m_nosync)
	{
		delay = 0;
	}

	delayTime(delay);

	m_curPoint.reset(getClock(), pts);

	//CLog::debug("current point(%lld, %lld). duration: %lld,%lld, offset: %lld\n",
	//	m_curPoint.m_clock, m_curPoint.m_pts,
	//	m_curPoint.m_clock - m_originPoint.m_clock, m_curPoint.m_pts - m_originPoint.m_pts, 
	//	(m_curPoint.m_pts - m_originPoint.m_pts) - (m_curPoint.m_clock - m_originPoint.m_clock) );

	drawFrame(frame, pts);
}

void CVideoRender::doStop()
{
	util::CommandThread::doStop();

	m_event.post();
	m_eventDelay.post();
}

void CVideoRender::setState(StreamState state)
{
	{
		comn::AutoCritSec lock(m_cs);
		m_state = state;
	}

	m_event.post();
	m_eventDelay.post();
}

AVFramePtr CVideoRender::waitAndGet(int ms)
{
	AVFramePtr frame = m_frames.pop();
	if (!frame)
	{
		if (m_event.timedwait(ms))
		{
			frame = m_frames.pop();
		}
	}
	return frame;
}

int CVideoRender::computeDelay(AVFramePtr& frame)
{
	int64_t pts = getPts(frame);
	TimePoint curPoint(getClock(), pts);

	TimePoint refPoint(m_originPoint);
	getRefPoint(refPoint);

	if (!refPoint.isSet() || refPoint.m_pts == AV_NOPTS_VALUE)
	{
		return frame->pkt_duration;
	}

	int delay = curPoint.getOffset(refPoint);
	delay -= 100 * 1000; /// 提前音频多少毫秒

	int lastDelay = 0;
	if (m_curPoint.isSet() && (m_curPoint.m_pts != AV_NOPTS_VALUE))
	{
		lastDelay = curPoint.getOffset(m_curPoint);
	}

	//CLog::debug("av sync. delay:%d, lastDelay:%d, delta:%d\n", delay/1000, lastDelay/1000, (delay-lastDelay)/1000);

	delay = (delay + lastDelay) / 2;

	if (delay >= MAX_SYNC_TIME)
	{
		delay = lastDelay;
	}

	return delay;
}

void CVideoRender::delayTime(int microseconds)
{
	if (microseconds <= 0)
	{
		return;
	}

	m_eventDelay.timedwait(microseconds / 1000);
}

int64_t CVideoRender::getPts(AVFramePtr& frame)
{
	int64_t pts = av_frame_get_best_effort_timestamp(frame.get());

	return pts;
}

bool CVideoRender::getRefPoint(TimePoint& pt)
{
	if (m_refClock)
	{
		m_refClock->getTimePoint(pt.m_clock, pt.m_pts);
	}

	if (!pt.isSet())
	{
		pt.reset(m_originPoint);
	}
	return true;
}

void CVideoRender::handlePlay()
{
	m_originPoint.reset();
	m_curPoint.reset();
}

void CVideoRender::handlePause()
{
	m_originPoint.reset();
	m_curPoint.reset();
}

void CVideoRender::handleStop()
{
	m_originPoint.reset();
	m_curPoint.reset();

	m_scale = av_make_q(1, 1);
}

void CVideoRender::handleStep()
{
	AVFramePtr frame = waitAndGet(-1);
	if (!frame)
	{
		return;
	}

	int64_t pts = getPts(frame);
	m_curPoint.reset(getClock(), pts);

	drawFrame(frame, pts);

}

void CVideoRender::handleNextKeyFrame()
{
	m_waitForKeyFrame = true;
}

void CVideoRender::handleSetScale(AVRational scale)
{
	m_scale = scale;

	m_originPoint.reset();
	m_curPoint.reset();
}

int64_t CVideoRender::getClock()
{
	int64_t clk = util::TimeHelper::getClock();
	if ((m_scale.num > 0) && (m_scale.num != m_scale.den))
	{
		clk = av_rescale(clk, m_scale.num, m_scale.den);
	}
	return clk;
}

VideoWindow* CVideoRender::createVideoWindow()
{
#ifdef WIN32
	return new WinVideoWindow();
#else
	return new SdlVideoWindow();
#endif//
}

void CVideoRender::drawFrame(AVFramePtr& frame, int64_t pts)
{
	if (!m_videoWindow)
	{
		return;
	}

	m_videoWindow->draw(frame.get());
}

bool CVideoRender::isExpired(AVPacketPtr& pkt, int64_t pts)
{
	return (pkt->pts + pkt->duration) < pts;
}


void CVideoRender::enableDrawGraph(int mode)
{
	m_drawGraphMode = mode;

	if (!m_videoWindow)
	{
		return;
	}

	m_videoWindow->enableDrawGraph(mode);
}

void CVideoRender::setDrawGraph(const char* value, size_t size)
{
	if (!m_videoWindow)
	{
		return;
	}

	m_videoWindow->setDrawGraph(value, size);
}

const char* CVideoRender::getDrawGraph()
{
	if (!m_videoWindow)
	{
		return "";
	}

	return m_videoWindow->getDrawGraph();
}

void CVideoRender::onRenderEvent(const RenderEvent& event)
{
	if (m_eventSink)
	{
		m_eventSink->onRenderEvent(event);
	}
}

void CVideoRender::onMouseButtonDown(int which, int x, int y, int modifier)
{
	RenderEvent event = RenderEvent();
	event.event = RENDER_EVENT_MOUSE_DOWN;
	event.x = x;
	event.y = y;
	event.which = which;
	event.modifier = modifier;
	onRenderEvent(event);
}

void CVideoRender::onMouseButtonUp(int which, int x, int y)
{
	RenderEvent event = RenderEvent();
	event.event = RENDER_EVENT_MOUSE_UP;
	event.x = x;
	event.y = y;
	event.which = which;
	onRenderEvent(event);
}

void CVideoRender::onMouseButtonDblClick(int which, int x, int y)
{
	RenderEvent event = RenderEvent();
	event.event = RENDER_EVENT_MOUSE_DOUBLE_DOWN;
	event.x = x;
	event.y = y;
	event.which = which;
	onRenderEvent(event);
}

void CVideoRender::onMouseEnter(int x, int y)
{
	RenderEvent event = RenderEvent();
	event.event = RENDER_EVENT_MOUSE_ENTER;
	event.x = x;
	event.y = y;
	onRenderEvent(event);
}

void CVideoRender::onMouseLeave(int x, int y)
{
	RenderEvent event = RenderEvent();
	event.event = RENDER_EVENT_MOUSE_LEAVE;
	event.x = x;
	event.y = y;
	onRenderEvent(event);
}


} /* namespace av */

